﻿using System;
using System.Windows.Forms;
using NAudio.Wave;
using System.Drawing;
using System.Threading;
using NAudio.Wave.SampleProviders;
using NAudio.Dsp;
using System.IO;
using System.Reflection;
using System.Collections.Generic;

namespace DGEMS
{
    public partial class DGEMS : Form
    {
        Thread tunerThrd;
        WaveIn wavStream;
        BufferedWaveProvider bufferedWaveProvider;
        WaveOut wo;
        Note[] notes;
        int inVolume;
        int outVolume;

        public DGEMS()
        {
            InitializeComponent();
            MakeNotesArray();
        }

        public void MakeNotesArray()
        {
            StreamReader stream = new StreamReader(
                Assembly.GetExecutingAssembly().GetManifestResourceStream(
                "DGEMS.notes.csv"
                ));
            notes = new Note[37];
            string line = null;
            int i = 0;
            while ((line = stream.ReadLine()) != null)
            {
                string[] split = line.Split(',');
                notes[i] = new Note(split[0], Double.Parse(split[1]), Double.Parse(split[2]), Double.Parse(split[3]));
                i++;
            }
        }

        public double MapTo(double value, double a1, double a2, double b1, double b2)
        {
            return b1 + ((value - a1) * (b2 - b1)) / (a2 - a1);
        }

        public int FindNote(double freq)
        {
            if (freq < notes[0].GetStart() || freq > notes[36].GetEnd())
            {
                return -1;
            }

            else
            {
                int first = 0;
                int last = notes.Length - 1;
                int mid = 0;

                bool found = false;
                while (first <= last && !found)
                {
                    mid = (first + last) / 2;
                    if (freq > notes[mid].GetStart() && freq < notes[mid].GetEnd())
                        found = true;
                    else if (freq < notes[mid].GetStart())
                        last = mid - 1;
                    else
                        first = mid + 1;
                }

                if (found)
                    return mid;
                else
                    return -1;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {

            InVolTrck.Enabled = !InVolTrck.Enabled;
            OutVolTrck.Enabled = !OutVolTrck.Enabled;
            tuner1.Enabled = !tuner1.Enabled;
            tunerBox.Checked = !tunerBox.Checked;
            tunerBox.Enabled = !tunerBox.Enabled;



            wavStream = new WaveIn(WaveCallbackInfo.FunctionCallback());
            wavStream.WaveFormat = new WaveFormat(22050, 2);
            wavStream.NumberOfBuffers = 3;
            wavStream.BufferMilliseconds = 40;
            wo = new WaveOut();
            wo.NumberOfBuffers = 3;
            inVolume = InVolTrck.Value;
            outVolume = OutVolTrck.Value;
            wavStream.DataAvailable += new EventHandler<WaveInEventArgs>((s2, e2) =>
            {
                ProccessData(s2, e2);
            });

            //Create Buffered Wave Provider
            bufferedWaveProvider = new BufferedWaveProvider(wavStream.WaveFormat);
            bufferedWaveProvider.DiscardOnBufferOverflow = true;

            //Start Recording
            try
            {
                wo.Init(bufferedWaveProvider);
                wavStream.StartRecording();
                wo.Play();
            }
            catch (Exception ex)
            {
                MessageBox.Show("No recording device detected\nPlease check your device and restart the program.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
        }

        private void ProccessData(object s2, WaveInEventArgs e2)
        {
            //Add sample to the buffered provider

            byte[] buffer = e2.Buffer;
            var length = e2.Buffer.Length;
            int shortRange = short.MaxValue - short.MinValue;

            double inSum = 0;
            double outSum = 0;
            double[] samples = new double[e2.Buffer.Length / 2];
            double[] FFTSamples = new double[e2.Buffer.Length / 2];
            
            short[] int16 = new short[e2.Buffer.Length / 2];

            // Convert Bits to Doubles
            // Raise by In Volume Factor
            for (var j = 0; j < e2.BytesRecorded; j += 2)
            {
                int16[j / 2] = (short)((e2.Buffer[j + 1] << 8) | e2.Buffer[j]);
                FFTSamples[j / 2] = (double)int16[j / 2];
                FFTSamples[j / 2] /= 32768.0;
                samples[j / 2] = (((int16[j / 2] - short.MinValue) * 2f) / shortRange) - 1f;
                double inFactor = (inVolume / 100.0);
                // raise In Volume
                samples[j / 2] = samples[j / 2] * inFactor;

                // Hard limit to [-1.0f, +1.0f] range
                if (samples[j / 2] > 1)
                {
                    samples[j / 2] = 1;
                }
                else if (samples[j / 2] < -1)
                {
                    samples[j / 2] = -1;
                }

                inSum += (samples[j / 2] * samples[j / 2]);
            }

            // Calculate InVolume
            double rms = Math.Sqrt(inSum / (e2.BytesRecorded / 2));
            var decibel = 20 * Math.Log10(rms);
            int d = (int)(100 - Math.Abs(decibel));
            int temp = (int)MapTo(d, 50, 105, 0, 100);
            InVolMtr.Value = temp;

            // Do Effect here using samples[]

            //double[] processedSamples = SymClip(samples);
            //double[] processedSamples = FIRComb(samples);
            //double[] processedSamples = TubeSim(samples);
            //processedSamples = TubeSim(processedSamples);
            //processedSamples = SymClip(samples);

            double[] processedSamples = samples;

            // Raise by OutVolume
            // Convert doubles back to Bits
            byte[] output = new byte[e2.Buffer.Length];
            for (var i = 0; i < e2.Buffer.Length; i += 2)
            {
                double outFactor = (outVolume / 100.0);
                processedSamples[i / 2] = processedSamples[i / 2] * outFactor;

                // Hard limit to [-1.0f, +1.0f] range
                if (processedSamples[i / 2] > 1)
                {
                    processedSamples[i / 2] = 1;
                }
                else if (processedSamples[i / 2] < -1)
                {
                    processedSamples[i / 2] = -1;
                }
                outSum += (processedSamples[i / 2] * processedSamples[i / 2]);

                short int1 = (short)((((processedSamples[i / 2] + 1f) * shortRange) / 2f) + short.MinValue);
                // Put the short back into the buffer
                output[i] = (byte)int1;
                output[i + 1] = (byte)(int1 >> 8);
            }


            // Calculate OutVolume
            rms = Math.Sqrt(outSum / (e2.BytesRecorded / 2));
            decibel = 20 * Math.Log10(rms);
            d = (int)(100 - Math.Abs(decibel));
            temp = (int)MapTo(d, 50, 105, 0, 100);
            OutVolMtr.Value = temp;

            // Add Samples to Wave Provider
            bufferedWaveProvider.AddSamples(output, 0, length);

            if (tuner1.Enabled)
            {
                // Calculate FFT and find frequency for tuner
                double freq = TunerUtils.FindFundamentalFrequency(FFTSamples, wavStream.WaveFormat.SampleRate, 30, 600);
                UpdateFrequecyDisplays(freq);
            }
        }

        private double[] TubeSim(double[] samples)
        {
            int n = samples.Length;
            double gain = 0;
            double mix = 0;
            double z;
            double q;
            double[] y = new double[n];

            for (int i = 0; i < n; i++)
            {
                q = samples[i] * gain / .8;
                z = Math.Sign(-q) * (1 - Math.Exp(Math.Sign(-q) * q));
                y[i] = mix * z * .8 / .4 + (1 - mix) * samples[i];
                y[i] = y[i] * .8 / .02;
            }
            return y;
        }

        private double[] SymClip(double[] samples)
        {
            int n = samples.Length;
            double[] temp = new double[n];
            double thresh = (1.0 / 3.0);
            double gain = 2;
            int f = 5;
            for (int i = 0; i < n; i++)
            {
                double inSample = gain * samples[i];
                double absIn = Math.Abs(inSample);
                if (absIn < thresh)
                {
                    temp[i] = (inSample * 2);
                }
                else if (absIn < 2 * thresh)
                {
                    if (inSample > 0)
                    {
                        temp[i] = (f - (2 - inSample * f) * (2 - inSample * f)) / f;
                    }
                    else if (inSample < 0)
                        temp[i] = -(f - (2 - absIn * f) * (2 - absIn * f)) / f;
                }
                else if (absIn >= 2 * thresh)
                {
                    if (inSample > 0)
                    {
                        temp[i] = 1;
                    }
                    else if (inSample < 0)
                    {
                        temp[i] = -1;
                    }
                }
               // temp[i] = temp[i] / 100;
            }

            return temp;
        }


        private double[] FIRComb(double[] samples)
        {
            double delayLength = 10.0;
            double DELAY = Math.Round(delayLength * 22050.0);
            double WIDTH = Math.Round(10.0 * 22050.0);

            int n = samples.Length;

            int Length = (int)(2 + DELAY * 2);


            double[] y = new double[Length];
            double[] delay = new double[Length];
            for (int i = 0; i < Length; i++)
            {
                delay[i] = 0;
            }
            
            for (int i = 0; i < n; i++)
            {

                y[i] = samples[i] + (0.5) * delay[Length - 1];
                if (i > 0)
                {
                    if (i < delay.Length - 1)
                    {
                        for (int j = 0; j < i; j++)
                        {
                            delay[j + 1] = delay[j];
                        }
                        delay[i] = y[i];
                    }
                }
                else
                {
                    delay[i] = y[i];
                }
            }

            
            return y;
        }
        private void UpdateFrequecyDisplays(double frequency)
        {
            if (tunerThrd == null || !tunerThrd.IsAlive)
            {
                tunerThrd = new Thread(delegate() { FreqThread(frequency); });

                try
                {
                    tunerThrd.Start();
                }
                catch (ThreadStartException)
                {

                    return;
                }
            }
        }

        delegate void SetTunerCallback(double frequency);
        private void FreqThread(double frequency)
        {
            if (this.tuner1.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTunerCallback d = new SetTunerCallback(SetTuner);
                try
                {
                    this.Invoke
                        (d, new object[] { frequency });
                }
                catch (ObjectDisposedException ex)
                {
                    return;
                }
                catch (InvalidOperationException ex)
                {
                    return;
                }
            }
            else
            {
                return;
            }
        }
        // This method is passed in to the SetTunerCallBack delegate
        // to set the Value property of Tuner.
        private void SetTuner(double frequency)
        {
            if (frequency > 0)
            {
                int index = FindNote(frequency);
                if (index == -1)
                {
                    return;
                }
                float angle = (float)MapTo(frequency, notes[index].GetStart(), notes[index].GetEnd(), 70, 115);
                if (angle >= 80 && angle <= 100)
                {
                    angle = 90;
                }
                tuner1.Angle = angle;
                if (index == 0)
                {
                    tuner1.LowNote = "B";
                }
                else
                {
                    tuner1.LowNote = notes[index - 1].GetName();
                }
                tuner1.Note = notes[index].GetName();

                if (index == notes.Length - 1)
                {
                    tuner1.HighNote = "C#";
                }
                else
                {
                    tuner1.HighNote = notes[index + 1].GetName();
                }

                tuner1.Frequency = frequency;
                Thread.Sleep(100);
            }
            else
            {
                return;
            }
        }

        private void DGEMS_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (tunerThrd != null && tunerThrd.IsAlive)
            {
                tunerThrd.Abort();
            }
            if (wo != null && wo.PlaybackState == PlaybackState.Playing)
                wo.Stop();
        }

        private void InVolTrck_ValueChanged(object sender, EventArgs e)
        {
            inVolume = InVolTrck.Value;
        }

        private void OutVolTrck_ValueChanged(object sender, EventArgs e)
        {
            outVolume = OutVolTrck.Value;
        }

        private void TunerBox_CheckedChanged(object sender, EventArgs e)
        {
            tuner1.Enabled = !tuner1.Enabled;
        }

        private void DGEMS_Load(object sender, EventArgs e)
        {
            int devices = WaveIn.DeviceCount;
            if (devices > 0)
            {
                for (int i = 0; i < devices; i++)
                {
                    WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(i);
                    DeviceBox.Items.Add(deviceInfo.ProductName);
                }
                DeviceBox.SelectedIndex = 0;
            }
            else
            {
                DeviceBox.Items.Add("No Device");
                DeviceBox.SelectedIndex = 0;
            }
        }

    }
}
